package com.lianup.noname.processor;

import com.lianup.noname.common.HttpConstant;
import com.lianup.noname.common.HttpHeaderEnum;
import com.lianup.noname.connector.HttpConnector;
import com.lianup.noname.deal.HttpRequest;
import com.lianup.noname.deal.HttpResponse;
import com.lianup.noname.util.RequestUtil;
import com.lianup.noname.util.StringUtil;
import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.LinkedList;
import java.util.Optional;
import java.util.Queue;
import java.util.stream.Stream;

import static java.util.stream.Collectors.toCollection;

/**
 * 每一个请求都对应一个HttpProcess，用来处理http请求信息，封装成request和response对象。
 * 故这里requst和response为成员对象了
 */
public class HttpProcessor implements Runnable{



    protected HttpConnector connector;
    private HttpRequest httpRequest;
    private HttpResponse httpResponse;
    private boolean available;
    private boolean stoped;
    private static final  String SERVLET_URL_START_WITH = "/servlet/";
    public Socket socket;
    private final static Object threadSync = new Object();


    public void process(Socket socket){

        try (InputStream inputStream = socket.getInputStream();
             OutputStream outputStream = socket.getOutputStream()){

            httpRequest = new HttpRequest(inputStream);
            httpResponse = new HttpResponse(outputStream,httpRequest);
            this.parseRequstLine(inputStream);
            this.parseRequestHead(inputStream);
            if(httpRequest.getRequestURI().startsWith(SERVLET_URL_START_WITH)){
                new ServeletProcessor().process(httpRequest,httpResponse);
            }else {
                new StaticResoucesProcessor().process(httpRequest,httpResponse);
            }
            // 处理完关闭套接字
            socket.close();
        }catch (Exception e){
            e.printStackTrace();
        }

    }

    /**
     * 处理各种参数，并且设置各种参数。
     * @param reqStream
     */
    private void parseRequestHead(InputStream reqStream){

        StringBuilder sb = new StringBuilder();
        int cache;
        try {
            while ((cache = reqStream.read()) > -1 && reqStream.available() > 0){
                sb.append((char)cache);
//                if(sb.length() >= 4){
//                    int length = sb.length();
//                    if(sb.charAt(length-1) == '\r' && sb.charAt(length-2) == '\n' && sb.charAt(length-3) == '\r'
//                            ){
//                        System.out.println("finish");
//                        break;
//                    }
//                }

            }
            // 原本做法
            //使用\r\n分割请求头\
        Queue<String> headers = Stream.of(sb.toString().split("\r\n")).collect(toCollection(LinkedList::new));
        //获取一个请求头
        while (!headers.isEmpty()) {
            String headerString = headers.poll();
            //读取到空行则说明请求头已读取完毕
            if (StringUtil.isBlank(headerString)) {
                break;
            }
            //分割请求头的key和value
            String[] headerKeyValue = headerString.split(": ");
            httpRequest.addHeader(headerKeyValue[0], headerKeyValue[1]);
        }

        //如果在读取到空行后还有数据，说明是POST请求的表单参数
        if (!headers.isEmpty()) {
            httpRequest.setPostParams(headers.poll());
        }



            //设置请求参数
            String contentLength = httpRequest.getHeader(HttpHeaderEnum.CONTENT_LENGTH.getDesc());
            if (contentLength != null) {
                httpRequest.setContentLength(Integer.parseInt(contentLength));
            }
            httpRequest.setContentType(httpRequest.getHeader(HttpHeaderEnum.CONTENT_TYPE.getDesc()));
            httpRequest.setCharacterEncoding(RequestUtil.parseCharacterEncoding(httpRequest.getHeader(httpRequest.getContentType())));

            Cookie[] cookies = parseCookieHeader(httpRequest.getHeader(HttpHeaderEnum.COOKIE.getDesc()));
            Optional.ofNullable(cookies).ifPresent(cookie ->
                    Stream.of(cookie).forEach(c -> httpRequest.addCookie(c))
            );
            //如果sessionid不是从cookie中获取的，则优先使用cookie中的sessionid
            if (!httpRequest.isRequestedSessionIdFromCookie() && cookies != null) {
                Stream.of(cookies)
                        .filter(cookie -> "jsessionid".equals(cookie.getName()))
                        .findFirst().
                        ifPresent(cookie -> {
                            //设置cookie的值
                            httpRequest.setRequestedSessionId(cookie.getValue());
                            httpRequest.setRequestedSessionCookie(true);
                            httpRequest.setRequestedSessionURL(false);
                        });
            }
        }catch (Exception e){
            e.printStackTrace();
        }



    }



    private void parseRequstLine(InputStream reqStream) throws Exception{

        StringBuilder tempStore = new StringBuilder();
        int cache;
        try {
            // 读取请求行
            while ((cache = reqStream.read()) != -1){
                //当遇到第一个\r\n的时候，说明请求行读取结束
                if(HttpConstant.CARRIAGE_RETURN == cache && HttpConstant.LINE_FEED == reqStream.read()){
//                if(HttpConstant.CARRIAGE_RETURN == cache){
                    break;
                }
                tempStore.append((char)cache);
            }
//
            String[] requestLineArray = tempStore.toString().split(" ");
            if (requestLineArray.length < 3) {
                throw new ServletException("HTTP request line is not standard！");
            }

            // 填充request的URI和方法信息
            httpRequest.setMethod(requestLineArray[0]);
            httpRequest.setProtocol(requestLineArray[2]);
            // 处理请求行信息，判断是否有?，如果有则把后面你的参数放到request的qureyString中。
            String uri = requestLineArray[1];
            /**
             * 网页会有一个网页图标，它会在网页请求后请求获取这个网页图标，如果获取不到则会一直报错，所以自己
             * 放一个在static里面就可以避免报错了。
             */
            int question = uri.indexOf("?");
            if(question >= 0){
                httpRequest.setQueryString(uri.substring(question + 1, uri.length()));
                uri = uri.substring(0, question);
            }
            // 解析查询字符串是否携带jsessionid，如果有则设置sessionid信息
            String match = ";jsessionid=";
            int semicolon = uri.indexOf(match);
            if (semicolon >= 0) {
                String rest = uri.substring(semicolon + match.length());
                int semicolon2 = rest.indexOf(';');
                if (semicolon2 >= 0) {
                    httpRequest.setRequestedSessionId(rest.substring(0, semicolon2));
                    rest = rest.substring(semicolon2);
                } else {
                    httpRequest.setRequestedSessionId(rest);
                    rest = "";
                }
                httpRequest.setRequestedSessionURL(true);
                uri = uri.substring(0, semicolon) + rest;
            } else {
                httpRequest.setRequestedSessionId(null);
                httpRequest.setRequestedSessionURL(false);
            }

            //校验URI有没有不符合规范或者不正常的地方，修正
            String normalizedUri = RequestUtil.normalize(uri);
            if (normalizedUri == null) {
                    throw new ServletException("Invalid URI: " + uri + "'");
                }
            httpRequest.setRequestURI(normalizedUri);
    }catch (Exception e){
            e.printStackTrace();
        }
    }


    /**
     * 将cookie字符串解析成cookie数组
     * @param cookieListString 请求头中的cookie字符串
     * @return cookie数组
     */
    private Cookie[] parseCookieHeader(String cookieListString) {
        if (StringUtil.isBlank(cookieListString)) {
            return null;
        }
        return Stream.of(cookieListString.split("; "))
                .map(cookieStr -> {
                    String[] cookieArray = cookieStr.split("=");
                    return new Cookie(StringUtil.urlDecode(cookieArray[0]), StringUtil.urlDecode(cookieArray[1]));
                }).toArray(Cookie[]::new);
    }


    public void setConnector(HttpConnector connector){
        this.connector = connector;
    }



    @Override
    public void run() {
        while(!stoped){
            // wait for the next socket to be assigned
            Socket socket = await();
            if(socket == null){
                continue;
            }
            process(socket);
            connector.recycleProcessor(this);
        }

        synchronized (threadSync){
            threadSync.notifyAll();
        }
    }

    public synchronized void assign(Socket socket){
        while(available){
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        this.socket = socket;
        available = true;
        notifyAll();
    }


    private synchronized Socket await(){
        while(!available){
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        Socket socket = this.socket;
        available = false;
        notifyAll();
        return socket;
    }

}
